spoko-design-system 0.8.9 → 0.9.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.
- 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 +114 -114
- 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 +41 -41
- 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 +8 -8
- package/public/locales/pl/translation.json +8 -8
- 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 -91
- 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/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/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/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 +259 -259
- 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,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>
|
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
---
|
|
2
|
-
const { productObject, locale, index } = Astro.props;
|
|
3
|
-
import Image from "./Image.astro"
|
|
4
|
-
import ProductNumber from "./Product/ProductNumber.astro"
|
|
5
|
-
import { t } from "i18next";
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
{ productObject &&
|
|
9
|
-
(
|
|
10
|
-
|
|
11
|
-
<!-- product image -->
|
|
12
|
-
<div class="img--4/3 img--small">
|
|
13
|
-
{ productObject.photo !== null ?
|
|
14
|
-
<Image
|
|
15
|
-
imageObject={
|
|
16
|
-
{
|
|
17
|
-
src: 'https://img.freepik.com/darmowe-wektory/tlo-retro-modeli-geometrycznych_52683-17902.jpg?w=1380&t=st=1706311337',
|
|
18
|
-
alt: 'Image Alt',
|
|
19
|
-
height: '180',
|
|
20
|
-
width: '240',
|
|
21
|
-
class: 'img--overlay object-cover'
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
/>
|
|
25
|
-
:
|
|
26
|
-
<img src="/1x1.png" class="bg-gray-100/70" alt={productObject.name} />
|
|
27
|
-
}
|
|
28
|
-
</div>
|
|
29
|
-
|
|
30
|
-
<!-- product deails -->
|
|
31
|
-
<div class="sm:pl-4 flex flex-col" >
|
|
32
|
-
<a class="font-light leading-none mb-2 pr-4 line-clamp-2 h-[2em] before:(content-empty absolute left-0 right-4 h-full top-0)"
|
|
33
|
-
href={productObject.url} itemprop="url"
|
|
34
|
-
title={productObject.number}
|
|
35
|
-
>
|
|
36
|
-
{ productObject.name }
|
|
37
|
-
</a>
|
|
38
|
-
|
|
39
|
-
<ProductNumber productNumber={productObject.number} copyDisabled={false} buttonTexts={{ copy: t('copy'), copied: t('copied') }} />
|
|
40
|
-
|
|
41
|
-
{ index !== null &&
|
|
42
|
-
( <meta itemprop="position" content={String(index)} />
|
|
43
|
-
<meta itemprop="name" content={productObject.name} /> )
|
|
44
|
-
}
|
|
45
|
-
</div>
|
|
46
|
-
|
|
47
|
-
)}
|
|
48
|
-
|
|
1
|
+
---
|
|
2
|
+
const { productObject, locale, index } = Astro.props;
|
|
3
|
+
import Image from "./Image.astro"
|
|
4
|
+
import ProductNumber from "./Product/ProductNumber.astro"
|
|
5
|
+
import { t } from "i18next";
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
{ productObject &&
|
|
9
|
+
(
|
|
10
|
+
|
|
11
|
+
<!-- product image -->
|
|
12
|
+
<div class="img--4/3 img--small">
|
|
13
|
+
{ productObject.photo !== null ?
|
|
14
|
+
<Image
|
|
15
|
+
imageObject={
|
|
16
|
+
{
|
|
17
|
+
src: 'https://img.freepik.com/darmowe-wektory/tlo-retro-modeli-geometrycznych_52683-17902.jpg?w=1380&t=st=1706311337',
|
|
18
|
+
alt: 'Image Alt',
|
|
19
|
+
height: '180',
|
|
20
|
+
width: '240',
|
|
21
|
+
class: 'img--overlay object-cover'
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/>
|
|
25
|
+
:
|
|
26
|
+
<img src="/1x1.png" class="bg-gray-100/70" alt={productObject.name} />
|
|
27
|
+
}
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<!-- product deails -->
|
|
31
|
+
<div class="sm:pl-4 flex flex-col" >
|
|
32
|
+
<a class="font-light leading-none mb-2 pr-4 line-clamp-2 h-[2em] before:(content-empty absolute left-0 right-4 h-full top-0)"
|
|
33
|
+
href={productObject.url} itemprop="url"
|
|
34
|
+
title={productObject.number}
|
|
35
|
+
>
|
|
36
|
+
{ productObject.name }
|
|
37
|
+
</a>
|
|
38
|
+
|
|
39
|
+
<ProductNumber productNumber={productObject.number} copyDisabled={false} buttonTexts={{ copy: t('copy'), copied: t('copied') }} />
|
|
40
|
+
|
|
41
|
+
{ index !== null &&
|
|
42
|
+
( <meta itemprop="position" content={String(index)} />
|
|
43
|
+
<meta itemprop="name" content={productObject.name} /> )
|
|
44
|
+
}
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
)}
|
|
48
|
+
|
package/src/components/Quote.vue
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { PropType } from 'vue';
|
|
3
|
-
|
|
4
|
-
const props = defineProps({
|
|
5
|
-
as: {
|
|
6
|
-
type: String as PropType<'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'div' | 'span'>,
|
|
7
|
-
default: 'div',
|
|
8
|
-
required: false,
|
|
9
|
-
},
|
|
10
|
-
text: {
|
|
11
|
-
type: String,
|
|
12
|
-
default: '',
|
|
13
|
-
required: false,
|
|
14
|
-
}
|
|
15
|
-
})
|
|
16
|
-
</script>
|
|
17
|
-
|
|
18
|
-
<template>
|
|
19
|
-
<component :is="props.as"
|
|
20
|
-
class="px-8 sm:px-8 mx-4 sm:mx-6 lg:max-w-4xl drop-shadow-primary text-2xl md:text-4xl lg:text-4.5xl relative font-light after:left-0 after:content-empty after:rounded-3xl after:top-0 after:absolute after:h-full after:border-solid after:border-[var(--primary)] after:border-l-3 after:z-0">
|
|
21
|
-
<slot>{{ props.text }}</slot>
|
|
22
|
-
</component>
|
|
23
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { PropType } from 'vue';
|
|
3
|
+
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
as: {
|
|
6
|
+
type: String as PropType<'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'div' | 'span'>,
|
|
7
|
+
default: 'div',
|
|
8
|
+
required: false,
|
|
9
|
+
},
|
|
10
|
+
text: {
|
|
11
|
+
type: String,
|
|
12
|
+
default: '',
|
|
13
|
+
required: false,
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<component :is="props.as"
|
|
20
|
+
class="px-8 sm:px-8 mx-4 sm:mx-6 lg:max-w-4xl drop-shadow-primary text-2xl md:text-4xl lg:text-4.5xl relative font-light after:left-0 after:content-empty after:rounded-3xl after:top-0 after:absolute after:h-full after:border-solid after:border-[var(--primary)] after:border-l-3 after:z-0">
|
|
21
|
+
<slot>{{ props.text }}</slot>
|
|
22
|
+
</component>
|
|
23
|
+
</template>
|