spoko-design-system 0.7.9 → 0.8.1

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 (167) hide show
  1. package/.github/dependabot.yml +11 -11
  2. package/.github/todo.yml +3 -3
  3. package/.github/workflows/deploy.yml +39 -39
  4. package/.stackblitzrc +5 -5
  5. package/.vscode/extensions.json +5 -5
  6. package/.vscode/launch.json +11 -11
  7. package/.vscode/settings.json +5 -5
  8. package/LICENSE +21 -21
  9. package/README.md +114 -114
  10. package/astro-i18next.config.mjs +17 -17
  11. package/astro-i18next.config.ts +10 -10
  12. package/astro.config.mjs +86 -86
  13. package/dev-dist/sw.js +91 -91
  14. package/dev-dist/workbox-c676b6d3.js +3391 -3391
  15. package/icon.config.ts +309 -309
  16. package/index.ts +66 -66
  17. package/package.json +23 -23
  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 +91 -91
  39. package/src/components/Button.vue +101 -101
  40. package/src/components/ButtonCopy.astro +183 -183
  41. package/src/components/ButtonCopy.vue +36 -36
  42. package/src/components/Card.astro +27 -27
  43. package/src/components/Carousel.astro +26 -26
  44. package/src/components/Category/CategoriesCarousel.astro +101 -101
  45. package/src/components/Category/CategoryDetails.astro +169 -169
  46. package/src/components/Category/CategoryLink.vue +28 -28
  47. package/src/components/Category/CategorySidebarToggler.vue +9 -9
  48. package/src/components/Category/CategoryTile.astro +37 -37
  49. package/src/components/Category/CategoryViewToggler.astro +89 -89
  50. package/src/components/Category/SubCategoryLink.vue +19 -19
  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 +80 -80
  55. package/src/components/FeaturesList.vue +37 -37
  56. package/src/components/FuckRussia.vue +62 -62
  57. package/src/components/HandDrive.astro +29 -29
  58. package/src/components/Header/Header.astro +210 -210
  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/LeftSidebar.astro +53 -53
  63. package/src/components/MainColors.vue +22 -22
  64. package/src/components/MainInput.vue +15 -15
  65. package/src/components/Modal.astro +27 -27
  66. package/src/components/PageContent.astro +5 -5
  67. package/src/components/PartNumber.vue +27 -27
  68. package/src/components/Post/PostCategories.astro +41 -41
  69. package/src/components/Post/PostCategories.vue +30 -30
  70. package/src/components/PostHeader.astro +103 -103
  71. package/src/components/PrCode.vue +141 -141
  72. package/src/components/Product/ProductButton.vue +18 -18
  73. package/src/components/Product/ProductCarousel.astro +35 -35
  74. package/src/components/Product/ProductEngineType.vue +42 -42
  75. package/src/components/Product/ProductImage.astro +40 -40
  76. package/src/components/Product/ProductLink.astro +101 -101
  77. package/src/components/Product/ProductLink.vue +59 -59
  78. package/src/components/Product/ProductLinkInfo.astro +37 -37
  79. package/src/components/Product/ProductNumber.astro +60 -60
  80. package/src/components/ProductCarousel.astro +38 -38
  81. package/src/components/ProductCodes.vue +39 -39
  82. package/src/components/ProductDetailName.vue +52 -52
  83. package/src/components/ProductDetailsList.vue +197 -130
  84. package/src/components/ProductTile.astro +48 -48
  85. package/src/components/Quote.vue +23 -23
  86. package/src/components/ReloadPrompt.astro +50 -50
  87. package/src/components/SlimBanner.vue +72 -72
  88. package/src/components/Table.vue +32 -32
  89. package/src/components/TableOfContents.astro +15 -15
  90. package/src/components/Translations.vue +23 -23
  91. package/src/components/flags/FlagPL.vue +3 -3
  92. package/src/components/flags/FlagUA.vue +2 -2
  93. package/src/components/layout/Container.astro +7 -7
  94. package/src/components/layout/Header.astro +80 -80
  95. package/src/config.ts +56 -56
  96. package/src/design.config.ts +98 -98
  97. package/src/env.d.ts +6 -6
  98. package/src/layouts/Layout.astro +61 -61
  99. package/src/layouts/MainLayout.astro +81 -81
  100. package/src/layouts/partials/FooterCommon.astro +4 -4
  101. package/src/layouts/partials/HeadCommon.astro +44 -44
  102. package/src/layouts/partials/HeadSEO.astro +41 -41
  103. package/src/pages/components/badges.mdx +57 -57
  104. package/src/pages/components/breadcrumbs.mdx +139 -139
  105. package/src/pages/components/buttons.mdx +360 -360
  106. package/src/pages/components/card.mdx +294 -294
  107. package/src/pages/components/carousel.mdx +62 -62
  108. package/src/pages/components/copyright.mdx +42 -42
  109. package/src/pages/components/details-list.mdx +115 -115
  110. package/src/pages/components/features-list.mdx +37 -37
  111. package/src/pages/components/flags.mdx +49 -49
  112. package/src/pages/components/fuck-russia.mdx +39 -39
  113. package/src/pages/components/hand-drive.mdx +38 -38
  114. package/src/pages/components/headline.mdx +152 -152
  115. package/src/pages/components/icons.astro +135 -135
  116. package/src/pages/components/image.mdx +513 -513
  117. package/src/pages/components/input.mdx +367 -367
  118. package/src/pages/components/jumbotron.mdx +359 -359
  119. package/src/pages/components/modal.mdx +64 -64
  120. package/src/pages/components/post-header.mdx +64 -64
  121. package/src/pages/components/pr-code.mdx +65 -65
  122. package/src/pages/components/product-number.mdx +58 -58
  123. package/src/pages/components/product-tile.mdx +51 -51
  124. package/src/pages/components/quote.mdx +33 -33
  125. package/src/pages/components/slimbanner.mdx +35 -35
  126. package/src/pages/components/table.mdx +108 -108
  127. package/src/pages/core/colors.mdx +10 -10
  128. package/src/pages/core/grid.mdx +89 -89
  129. package/src/pages/core/introduction.mdx +77 -77
  130. package/src/pages/core/shadows.astro +20 -20
  131. package/src/pages/core/typography.astro +49 -49
  132. package/src/pages/index.astro +133 -133
  133. package/src/pages/patterns/introduction.mdx +60 -60
  134. package/src/pwa.ts +12 -12
  135. package/src/styles/_variables.scss +70 -70
  136. package/src/styles/base/base.css +184 -184
  137. package/src/styles/base/grid.css +92 -92
  138. package/src/styles/base/typography.css +70 -70
  139. package/src/styles/content.css +73 -73
  140. package/src/styles/main.css +7 -7
  141. package/src/types/Product.ts +31 -31
  142. package/src/types/astro.d.ts +3 -3
  143. package/src/utils/product/getPriceFormatted.ts +15 -15
  144. package/src/utils/product/getProductChecklist.ts +17 -17
  145. package/src/utils/product/useFormatProductNumber.ts +41 -41
  146. package/src/utils/seo/getShorterDescription.ts +14 -14
  147. package/src/utils/text/formatDate.ts +5 -5
  148. package/src/utils/text/formatLocaleNumber.ts +6 -6
  149. package/src/utils/text/formatPad.ts +12 -12
  150. package/src/utils/text/getNumberFormatted.ts +33 -33
  151. package/src/utils/text/getTranslatedLink.ts +5 -5
  152. package/src/utils/text.ts +19 -19
  153. package/tailwind.config.cjs +8 -8
  154. package/tsconfig.json +28 -28
  155. package/uno-config/index.ts +259 -259
  156. package/uno-config/theme/breakpoints.ts +9 -9
  157. package/uno-config/theme/colors.ts +64 -64
  158. package/uno-config/theme/dimensions.ts +17 -17
  159. package/uno-config/theme/effects.ts +14 -14
  160. package/uno-config/theme/grid.ts +10 -10
  161. package/uno-config/theme/index.ts +28 -28
  162. package/uno-config/theme/shortcuts/buttons.ts +53 -53
  163. package/uno-config/theme/shortcuts/components.ts +123 -123
  164. package/uno-config/theme/shortcuts/index.ts +20 -20
  165. package/uno-config/theme/shortcuts/layout.ts +64 -64
  166. package/uno-config/theme/typography.ts +29 -29
  167. package/uno.config.ts +2 -2
@@ -1,38 +1,38 @@
1
- ---
2
- import ProductTile from "../components/ProductTile.astro";
3
- export const productObject = {
4
- name: "Combi-instrument MFA+",
5
- url: "https://google.com",
6
- number: "6R0920870F",
7
- photo:
8
- "https://img.freepik.com/darmowe-wektory/tlo-retro-modeli-geometrycznych_52683-17902.jpg?w=1380&t=st=1706311337",
9
- };
10
- ---
11
-
12
- <div class="bg-white rounded-lg p-4 w-full">
13
- <swiper-container
14
- class="flex w-full max-w-full"
15
- grid-rows="1"
16
- mousewheel-force-to-axis="true"
17
- navigation="false"
18
- scrollbar="false"
19
- slides-per-view="auto"
20
- space-between="0"
21
- >
22
- <swiper-slide class="carousel-product-tile">
23
- <ProductTile productObject={productObject} />
24
- </swiper-slide>
25
- <swiper-slide class="carousel-product-tile">
26
- <ProductTile productObject={productObject} />
27
- </swiper-slide>
28
- <swiper-slide class="carousel-product-tile">
29
- <ProductTile productObject={productObject} />
30
- </swiper-slide>
31
- <swiper-slide class="carousel-product-tile">
32
- <ProductTile productObject={productObject} />
33
- </swiper-slide>
34
- <swiper-slide class="carousel-product-tile">
35
- <ProductTile productObject={productObject} />
36
- </swiper-slide>
37
- </swiper-container>
38
- </div>
1
+ ---
2
+ import ProductTile from "../components/ProductTile.astro";
3
+ export const productObject = {
4
+ name: "Combi-instrument MFA+",
5
+ url: "https://google.com",
6
+ number: "6R0920870F",
7
+ photo:
8
+ "https://img.freepik.com/darmowe-wektory/tlo-retro-modeli-geometrycznych_52683-17902.jpg?w=1380&t=st=1706311337",
9
+ };
10
+ ---
11
+
12
+ <div class="bg-white rounded-lg p-4 w-full">
13
+ <swiper-container
14
+ class="flex w-full max-w-full"
15
+ grid-rows="1"
16
+ mousewheel-force-to-axis="true"
17
+ navigation="false"
18
+ scrollbar="false"
19
+ slides-per-view="auto"
20
+ space-between="0"
21
+ >
22
+ <swiper-slide class="carousel-product-tile">
23
+ <ProductTile productObject={productObject} />
24
+ </swiper-slide>
25
+ <swiper-slide class="carousel-product-tile">
26
+ <ProductTile productObject={productObject} />
27
+ </swiper-slide>
28
+ <swiper-slide class="carousel-product-tile">
29
+ <ProductTile productObject={productObject} />
30
+ </swiper-slide>
31
+ <swiper-slide class="carousel-product-tile">
32
+ <ProductTile productObject={productObject} />
33
+ </swiper-slide>
34
+ <swiper-slide class="carousel-product-tile">
35
+ <ProductTile productObject={productObject} />
36
+ </swiper-slide>
37
+ </swiper-container>
38
+ </div>
@@ -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,130 +1,197 @@
1
- <script setup lang="ts">
2
- import { PropType, computed } from "vue";
3
- import ProductDetailName from "./ProductDetailName.vue";
4
-
5
- interface TableItem {
6
- id: string;
7
- name: string;
8
- value: unknown;
9
- translated?: boolean;
10
- icon?: boolean;
11
- }
12
-
13
- interface GroupedLink {
14
- id: string;
15
- links: {
16
- name: string;
17
- value: string;
18
- }[];
19
- }
20
-
21
- const props = defineProps({
22
- items: { type: Array as PropType<TableItem[]>, default: () => [] },
23
- caption: { type: String, default: null }
24
- });
25
-
26
- // Function for checking whether a value is a link
27
- const isLink = (id: string) => {
28
- return ['blog', 'youtube', 'vimeo'].includes(id);
29
- };
30
-
31
- // Function for specifying header text
32
- const getHeaderText = (row: TableItem | GroupedLink) => {
33
- // For the blog, we use id instead of name
34
- if (row.id === 'blog') {
35
- return row.id.charAt(0).toUpperCase() + row.id.slice(1); // "Blog" z dużej litery
36
- }
37
-
38
- // For other types, we use name (if it is GroupedLink, there is no name)
39
- return 'name' in row ? row.name : row.id.charAt(0).toUpperCase() + row.id.slice(1);
40
- };
41
-
42
- // Function to determine the icon class for a link type
43
- const getLinkIconClass = (linkId: string) => {
44
- switch (linkId) {
45
- case 'blog':
46
- return 'i-lucide-book-text';
47
- case 'youtube':
48
- return 'i-simple-icons-youtube';
49
- case 'vimeo':
50
- return 'i-simple-icons-vimeo';
51
- default:
52
- return 'i-lucide-link';
53
- }
54
- };
55
-
56
- // Grouping of elements by id
57
- const groupedItems = computed(() => {
58
- const result: (TableItem | GroupedLink)[] = [];
59
- const linkGroups = new Map<string, GroupedLink>();
60
-
61
- // We process all elements
62
- props.items.forEach(item => {
63
- // If it's a link (blog, youtube, vimeo)
64
- if (isLink(item.id)) {
65
- // Add a link to the relevant group
66
- if (!linkGroups.has(item.id)) {
67
- linkGroups.set(item.id, {
68
- id: item.id,
69
- links: []
70
- });
71
- }
72
-
73
- // Dodajemy link do odpowiedniej grupy
74
- linkGroups.get(item.id)?.links.push({
75
- name: item.name,
76
- value: item.value as string
77
- });
78
- } else {
79
- // If it is not a link, we add it normally to the results
80
- result.push(item);
81
- }
82
- });
83
-
84
- // Add all link groups at the end
85
- linkGroups.forEach(group => {
86
- result.push(group);
87
- });
88
-
89
- return result;
90
- });
91
- </script>
92
-
93
- <template>
94
- <table class="details-table">
95
- <caption v-if="!!$slots.caption || caption">
96
- <slot name="caption">{{ caption }}</slot>
97
- </caption>
98
- <colgroup>
99
- <col class="details-table-col">
100
- <col class="details-table-col">
101
- </colgroup>
102
- <tbody>
103
- <tr v-for="row, index in groupedItems" :key="index" class="details-table-row">
104
- <!-- We use the getHeaderText function to specify the header text -->
105
- <ProductDetailName
106
- as="th"
107
- :text="getHeaderText(row)"
108
- class="details-table-header"
109
- />
110
-
111
- <!-- Handling link groups -->
112
- <td v-if="'links' in row" class="details-table-cell">
113
- <ul class="list-none p-0 m-0">
114
- <li v-for="(link, linkIndex) in row.links" :key="linkIndex" class="mb-2 last:mb-0 flex items-center">
115
- <span :class="[getLinkIconClass(row.id), 'leading-none inline-block mr-2 w-4 min-w-4 h-4 text-gray-400']" />
116
- <a :href="link.value" target="_blank" rel="noopener noreferrer" class="link-primary">
117
- {{ link.name }}
118
- </a>
119
- </li>
120
- </ul>
121
- </td>
122
-
123
- <!-- Support for standard types -->
124
- <slot v-else-if="'id' in row" :name="row.id">
125
- <td class="details-table-cell">{{ row.value }}</td>
126
- </slot>
127
- </tr>
128
- </tbody>
129
- </table>
130
- </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 PaintMark {
11
+ count: number;
12
+ color: string;
13
+ }
14
+
15
+ interface TableItem {
16
+ id: string;
17
+ name: string;
18
+ value: unknown;
19
+ translated?: boolean;
20
+ icon?: boolean;
21
+ isArrayValue?: boolean;
22
+ isColorArray?: boolean;
23
+ isPaintMarks?: boolean;
24
+ isGenericArray?: boolean;
25
+ }
26
+
27
+ interface GroupedLink {
28
+ id: string;
29
+ links: {
30
+ name: string;
31
+ value: string;
32
+ }[];
33
+ }
34
+
35
+ const props = defineProps({
36
+ items: { type: Array as PropType<TableItem[]>, default: () => [] },
37
+ caption: { type: String, default: null }
38
+ });
39
+
40
+ // Function for checking whether a value is a link
41
+ const isLink = (id: string) => {
42
+ return ['blog', 'youtube', 'vimeo'].includes(id);
43
+ };
44
+
45
+ // Function for checking if it's a color array
46
+ const isColorArray = (item: TableItem) => {
47
+ return item.isColorArray && Array.isArray(item.value);
48
+ };
49
+
50
+ // Function for checking if it's paint marks
51
+ const isPaintMarks = (item: TableItem) => {
52
+ return item.isPaintMarks && Array.isArray(item.value);
53
+ };
54
+
55
+ // Function for checking if it's a generic array
56
+ const isGenericArray = (item: TableItem) => {
57
+ return item.isGenericArray && Array.isArray(item.value);
58
+ };
59
+
60
+ // Function to check if value is HTML string (fallback)
61
+ const isHtmlValue = (value: unknown): boolean => {
62
+ return typeof value === 'string' && (value.includes('<span') || value.includes('<br>'));
63
+ };
64
+
65
+ // Function for specifying header text
66
+ const getHeaderText = (row: TableItem | GroupedLink) => {
67
+ // For the blog, we use id instead of name
68
+ if (row.id === 'blog') {
69
+ return row.id.charAt(0).toUpperCase() + row.id.slice(1);
70
+ }
71
+
72
+ // For other types, we use name (if it is GroupedLink, there is no name)
73
+ return 'name' in row ? row.name : row.id.charAt(0).toUpperCase() + row.id.slice(1);
74
+ };
75
+
76
+ // Function to determine the icon class for a link type
77
+ const getLinkIconClass = (linkId: string) => {
78
+ switch (linkId) {
79
+ case 'blog':
80
+ return 'i-lucide-book-text';
81
+ case 'youtube':
82
+ return 'i-simple-icons-youtube';
83
+ case 'vimeo':
84
+ return 'i-simple-icons-vimeo';
85
+ default:
86
+ return 'i-lucide-link';
87
+ }
88
+ };
89
+
90
+ // Grouping of elements by id
91
+ const groupedItems = computed(() => {
92
+ const result: (TableItem | GroupedLink)[] = [];
93
+ const linkGroups = new Map<string, GroupedLink>();
94
+
95
+ // We process all elements
96
+ props.items.forEach(item => {
97
+ // If it's a link (blog, youtube, vimeo)
98
+ if (isLink(item.id)) {
99
+ // Add a link to the relevant group
100
+ if (!linkGroups.has(item.id)) {
101
+ linkGroups.set(item.id, {
102
+ id: item.id,
103
+ links: []
104
+ });
105
+ }
106
+
107
+ // Add link to the relevant group
108
+ linkGroups.get(item.id)?.links.push({
109
+ name: item.name,
110
+ value: item.value as string
111
+ });
112
+ } else {
113
+ // If it is not a link, we add it normally to the results
114
+ result.push(item);
115
+ }
116
+ });
117
+
118
+ // Add all link groups at the end
119
+ linkGroups.forEach(group => {
120
+ result.push(group);
121
+ });
122
+
123
+ return result;
124
+ });
125
+ </script>
126
+
127
+ <template>
128
+ <table class="details-table">
129
+ <caption v-if="!!$slots.caption || caption">
130
+ <slot name="caption">{{ caption }}</slot>
131
+ </caption>
132
+ <colgroup>
133
+ <col class="details-table-col">
134
+ <col class="details-table-col">
135
+ </colgroup>
136
+ <tbody>
137
+ <tr v-for="row, index in groupedItems" :key="index" class="details-table-row">
138
+ <!-- We use the getHeaderText function to specify the header text -->
139
+ <ProductDetailName
140
+ as="th"
141
+ :text="getHeaderText(row)"
142
+ class="details-table-header"
143
+ />
144
+
145
+ <!-- Handling link groups -->
146
+ <td v-if="'links' in row" class="details-table-cell">
147
+ <ul class="list-none p-0 m-0">
148
+ <li v-for="(link, linkIndex) in row.links" :key="linkIndex" class="mb-2 last:mb-0 flex items-center">
149
+ <span :class="[getLinkIconClass(row.id), 'leading-none inline-block mr-2 w-4 min-w-4 h-4 text-gray-400']" />
150
+ <a :href="link.value" target="_blank" rel="noopener noreferrer" class="link-primary">
151
+ {{ link.name }}
152
+ </a>
153
+ </li>
154
+ </ul>
155
+ </td>
156
+
157
+ <!-- Special handling for color arrays (already translated) -->
158
+ <td v-else-if="'id' in row && isColorArray(row)" class="details-table-cell">
159
+ <ul>
160
+ <li v-for="(colorItem, colorIndex) in (row.value as ColorCode[])" :key="colorIndex"
161
+ class="flex items-center gap-2 mb-1 last:mb-0">
162
+ <code class="bg-gray-100 dark:bg-gray-800 px-1 py-0.5 rounded text-sm font-mono font-semibold">
163
+ {{ colorItem.code }}
164
+ </code>
165
+ <span class="text-gray-400">-</span>
166
+ <span class="text-gray-700 dark:text-gray-300">{{ colorItem.name }}</span>
167
+ </li>
168
+ </ul>
169
+ </td>
170
+
171
+ <!-- Special handling for paint marks (already translated) -->
172
+ <td v-else-if="'id' in row && isPaintMarks(row)" class="details-table-cell">
173
+ <span class="text-gray-700 dark:text-gray-300">{{ row.value }}</span>
174
+ </td>
175
+
176
+ <!-- Generic array handling (bullet list) -->
177
+ <td v-else-if="'id' in row && isGenericArray(row)" class="details-table-cell">
178
+ <ul class="list-none p-0 m-0">
179
+ <li v-for="(item, itemIndex) in (row.value as string[])" :key="itemIndex"
180
+ class="flex items-start gap-2 mb-1 last:mb-0 leading-relaxed">
181
+ <span class="text-gray-500 font-bold flex-shrink-0 mt-0.5">·</span>
182
+ <span class="text-gray-700 dark:text-gray-300 text-sm">{{ item }}</span>
183
+ </li>
184
+ </ul>
185
+ </td>
186
+
187
+ <!-- Handling HTML values (fallback for already formatted HTML) -->
188
+ <td v-else-if="'id' in row && isHtmlValue(row.value)" class="details-table-cell" v-html="row.value"></td>
189
+
190
+ <!-- Support for standard types -->
191
+ <slot v-else-if="'id' in row" :name="row.id">
192
+ <td class="details-table-cell">{{ row.value }}</td>
193
+ </slot>
194
+ </tr>
195
+ </tbody>
196
+ </table>
197
+ </template>