spoko-design-system 0.9.2 → 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.
- package/.astro/settings.json +4 -4
- package/.astro/types.d.ts +1 -1
- package/.claude/settings.local.json +2 -1
- package/.github/dependabot.yml +11 -11
- package/.github/todo.yml +3 -3
- package/.github/workflows/deploy.yml +39 -39
- package/.stackblitzrc +5 -5
- package/.vscode/extensions.json +5 -5
- package/.vscode/launch.json +11 -11
- package/.vscode/settings.json +5 -5
- package/LICENSE +21 -21
- package/README.md +116 -116
- package/astro-i18next.config.mjs +17 -17
- package/astro-i18next.config.ts +10 -10
- package/astro.config.mjs +86 -86
- package/dev-dist/sw.js +91 -91
- package/dev-dist/workbox-c676b6d3.js +3391 -3391
- package/icon.config.ts +310 -310
- package/index.ts +70 -70
- package/package.json +124 -124
- package/public/arrow-bottom.svg +7 -7
- package/public/fonts/lg.svg +53 -53
- package/public/fonts/vwhead-bold-demo.html +549 -549
- package/public/fonts/vwhead-regular-demo.html +549 -549
- package/public/fonts/vwtext-bold-demo.html +549 -549
- package/public/fonts/vwtext-regular-demo.html +549 -549
- package/public/github.svg +3 -3
- package/public/grid_dot.svg +4 -4
- package/public/linkedin.svg +44 -44
- package/public/locales/en/translation.json +12 -12
- package/public/locales/pl/translation.json +12 -12
- package/public/make-scrollable-code-focusable.js +3 -3
- package/public/pagefind.yml +3 -3
- package/public/polo.blue.svg +29 -29
- package/public/spoko.space.svg +71 -71
- package/public/twitter.svg +46 -46
- package/renovate.json +6 -6
- package/sandbox.config.json +11 -11
- package/src/MyComponent.astro +8 -8
- package/src/components/Badge.vue +19 -19
- package/src/components/Badges.vue +21 -21
- package/src/components/Breadcrumbs.vue +94 -94
- package/src/components/Button.vue +101 -101
- package/src/components/ButtonCopy.astro +183 -183
- package/src/components/ButtonCopy.vue +36 -36
- package/src/components/Card.astro +27 -27
- package/src/components/Carousel.astro +26 -26
- package/src/components/Category/CategoriesCarousel.astro +101 -101
- package/src/components/Category/CategoryDetails.astro +169 -169
- package/src/components/Category/CategoryLink.vue +28 -28
- package/src/components/Category/CategorySidebarToggler.vue +9 -9
- package/src/components/Category/CategoryTile.astro +37 -37
- package/src/components/Category/CategoryViewToggler.astro +89 -89
- package/src/components/Category/SubCategoryLink.vue +19 -19
- package/src/components/Copyright.astro +12 -12
- package/src/components/Date.astro +7 -7
- package/src/components/Faq.astro +33 -33
- package/src/components/FaqItem.astro +80 -80
- package/src/components/FeaturesList.vue +37 -37
- package/src/components/FuckRussia.vue +62 -62
- package/src/components/HandDrive.astro +29 -29
- package/src/components/Header/Header.astro +210 -210
- package/src/components/Header/SkipToContent.astro +1 -1
- package/src/components/Headline.vue +87 -87
- package/src/components/Image.astro +30 -30
- package/src/components/{layout → Layout}/CallToAction.astro +52 -52
- package/src/components/{layout → Layout}/Container.astro +7 -7
- package/src/components/{layout → Layout}/Header.astro +80 -80
- package/src/components/LeftSidebar.astro +53 -53
- package/src/components/MainColors.vue +22 -22
- package/src/components/MainInput.vue +15 -15
- package/src/components/Modal.astro +27 -27
- package/src/components/PageContent.astro +5 -5
- package/src/components/PartNumber.vue +27 -27
- package/src/components/Post/PostCategories.astro +41 -41
- package/src/components/Post/PostCategories.vue +30 -30
- package/src/components/PostHeader.astro +103 -103
- package/src/components/PrCode.vue +141 -141
- package/src/components/Product/ProductButton.vue +18 -18
- package/src/components/Product/ProductCarousel.astro +35 -35
- package/src/components/Product/ProductEngineType.vue +42 -42
- package/src/components/Product/ProductImage.astro +40 -40
- package/src/components/Product/ProductLink.astro +101 -101
- package/src/components/Product/ProductLink.vue +59 -59
- package/src/components/Product/ProductLinkInfo.astro +37 -37
- package/src/components/Product/ProductNumber.astro +60 -60
- package/src/components/ProductCarousel.astro +38 -38
- package/src/components/ProductCodes.vue +39 -39
- package/src/components/ProductDetailName.vue +52 -52
- package/src/components/ProductDetailsList.vue +216 -216
- package/src/components/ProductTile.astro +48 -48
- package/src/components/Quote.vue +23 -23
- package/src/components/ReloadPrompt.astro +50 -50
- package/src/components/SlimBanner.vue +72 -72
- package/src/components/Table.vue +32 -32
- package/src/components/TableOfContents.astro +15 -15
- package/src/components/Translations.vue +23 -23
- package/src/components/flags/FlagPL.vue +3 -3
- package/src/components/flags/FlagUA.vue +2 -2
- package/src/config.ts +56 -56
- package/src/design.config.ts +98 -98
- package/src/env.d.ts +6 -6
- package/src/layouts/Layout.astro +61 -61
- package/src/layouts/MainLayout.astro +81 -81
- package/src/layouts/partials/FooterCommon.astro +4 -4
- package/src/layouts/partials/HeadCommon.astro +44 -44
- package/src/layouts/partials/HeadSEO.astro +41 -41
- package/src/pages/components/badges.mdx +57 -57
- package/src/pages/components/breadcrumbs.mdx +139 -139
- package/src/pages/components/buttons.mdx +360 -360
- package/src/pages/components/card.mdx +294 -294
- package/src/pages/components/carousel.mdx +62 -62
- package/src/pages/components/copyright.mdx +42 -42
- package/src/pages/components/details-list.mdx +115 -115
- package/src/pages/components/features-list.mdx +37 -37
- package/src/pages/components/flags.mdx +49 -49
- package/src/pages/components/fuck-russia.mdx +39 -39
- package/src/pages/components/hand-drive.mdx +38 -38
- package/src/pages/components/headline.mdx +137 -137
- package/src/pages/components/icons.astro +135 -135
- package/src/pages/components/image.mdx +513 -513
- package/src/pages/components/input.mdx +367 -367
- package/src/pages/components/jumbotron.mdx +359 -359
- package/src/pages/components/modal.mdx +64 -64
- package/src/pages/components/post-header.mdx +64 -64
- package/src/pages/components/pr-code.mdx +65 -65
- package/src/pages/components/product-number.mdx +58 -58
- package/src/pages/components/product-tile.mdx +51 -51
- package/src/pages/components/quote.mdx +33 -33
- package/src/pages/components/slimbanner.mdx +35 -35
- package/src/pages/components/table.mdx +108 -108
- package/src/pages/core/colors.mdx +10 -10
- package/src/pages/core/grid.mdx +89 -89
- package/src/pages/core/introduction.mdx +77 -77
- package/src/pages/core/shadows.astro +20 -20
- package/src/pages/core/typography.astro +49 -49
- package/src/pages/index.astro +133 -133
- package/src/pages/patterns/introduction.mdx +60 -60
- package/src/pwa.ts +12 -12
- package/src/styles/_variables.scss +70 -70
- package/src/styles/base/base.css +184 -184
- package/src/styles/base/grid.css +92 -92
- package/src/styles/base/typography.css +70 -70
- package/src/styles/content.css +73 -73
- package/src/styles/main.css +7 -7
- package/src/types/Product.ts +31 -31
- package/src/types/astro.d.ts +3 -3
- package/src/utils/product/getPriceFormatted.ts +15 -15
- package/src/utils/product/getProductChecklist.ts +17 -17
- package/src/utils/product/useFormatProductNumber.ts +41 -41
- package/src/utils/seo/getShorterDescription.ts +14 -14
- package/src/utils/text/formatDate.ts +5 -5
- package/src/utils/text/formatLocaleNumber.ts +6 -6
- package/src/utils/text/formatPad.ts +12 -12
- package/src/utils/text/getNumberFormatted.ts +33 -33
- package/src/utils/text/getTranslatedLink.ts +5 -5
- package/src/utils/text.ts +19 -19
- package/tailwind.config.cjs +8 -8
- package/tsconfig.json +28 -28
- package/uno-config/index.ts +268 -266
- package/uno-config/theme/breakpoints.ts +9 -9
- package/uno-config/theme/colors.ts +64 -64
- package/uno-config/theme/dimensions.ts +17 -17
- package/uno-config/theme/effects.ts +14 -14
- package/uno-config/theme/grid.ts +10 -10
- package/uno-config/theme/index.ts +28 -28
- package/uno-config/theme/shortcuts/buttons.ts +53 -53
- package/uno-config/theme/shortcuts/components.ts +123 -123
- package/uno-config/theme/shortcuts/index.ts +20 -20
- package/uno-config/theme/shortcuts/layout.ts +74 -74
- package/uno-config/theme/typography.ts +29 -29
- 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>
|