spoko-design-system 0.3.8 → 0.4.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/.astro/data-store.json +1 -1
- package/astro.config.mjs +61 -63
- package/index.ts +1 -0
- package/package.json +4 -4
- package/src/components/ButtonCopy.astro +165 -0
- package/src/components/Category/CategoryDetails.astro +74 -49
- package/src/components/Category/CategoryViewToggler.astro +25 -19
- package/src/components/Jumbotron/index.astro +1 -1
- package/src/components/Product/ProductNumber.astro +1 -2
- package/uno-config/theme/shortcuts/jumbotron.ts +1 -1
package/.astro/data-store.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.
|
|
1
|
+
[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.3.0","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"site\":\"https://sds.spoko.space/\",\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":1234,\"streaming\":true},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[\"placehold.co\",\"api.polo.blue\",\"polo.blue\",\"media.istockphoto.com\",\"img.freepik.com\",\"polo6r.pl\"],\"remotePatterns\":[]},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":\"shiki\",\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"responsiveImages\":false,\"serializeConfig\":false},\"legacy\":{\"collections\":false}}"]
|
package/astro.config.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import sitemap from "@astrojs/sitemap";
|
|
|
9
9
|
import pagefind from "astro-pagefind";
|
|
10
10
|
import AstroPWA from '@vite-pwa/astro';
|
|
11
11
|
import metaTags from "astro-meta-tags";
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
import { createSdsConfig } from './uno-config';
|
|
14
14
|
|
|
15
15
|
const unoConfig = createSdsConfig();
|
|
@@ -22,68 +22,66 @@ export default defineConfig({
|
|
|
22
22
|
},
|
|
23
23
|
image: {
|
|
24
24
|
service: sharpImageService(),
|
|
25
|
-
domains: ["placehold.co", "api.polo.blue", "polo.blue", "media.istockphoto.com", "img.freepik.com"]
|
|
25
|
+
domains: ["placehold.co", "api.polo.blue", "polo.blue", "media.istockphoto.com", "img.freepik.com", "polo6r.pl"]
|
|
26
26
|
},
|
|
27
27
|
integrations: [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
sitemap()
|
|
88
|
-
]
|
|
28
|
+
// Enable Vue to support Vue3 components
|
|
29
|
+
vue(),
|
|
30
|
+
mdx(),
|
|
31
|
+
astroI18next(),
|
|
32
|
+
AstroPWA({
|
|
33
|
+
mode: 'production',
|
|
34
|
+
base: '/',
|
|
35
|
+
scope: '/',
|
|
36
|
+
includeAssets: ['favicon.svg', 'safari-pinned-tab.svg', 'brands/*.svg', 'fonts/*.woff2', 'fonts/*.svg', 'vw.svg', 'polo.blue.svg', 'spoko.space.svg'],
|
|
37
|
+
registerType: 'autoUpdate',
|
|
38
|
+
manifest: {
|
|
39
|
+
name: 'Spoko Design System',
|
|
40
|
+
short_name: 'SDS',
|
|
41
|
+
description: 'SDS PWA app description',
|
|
42
|
+
categories: ['multimedia'],
|
|
43
|
+
screenshots: [{
|
|
44
|
+
"src": "pwa-512x512.png",
|
|
45
|
+
"sizes": "512x512",
|
|
46
|
+
"platform": "windows",
|
|
47
|
+
"label": "SDS"
|
|
48
|
+
}],
|
|
49
|
+
theme_color: '#001e50',
|
|
50
|
+
icons: [{
|
|
51
|
+
src: 'pwa-192x192.png',
|
|
52
|
+
sizes: '192x192',
|
|
53
|
+
type: 'image/png'
|
|
54
|
+
}, {
|
|
55
|
+
src: 'pwa-512x512.png',
|
|
56
|
+
sizes: '512x512',
|
|
57
|
+
type: 'image/png'
|
|
58
|
+
}, {
|
|
59
|
+
src: 'pwa-512x512.png',
|
|
60
|
+
sizes: '512x512',
|
|
61
|
+
type: 'image/png',
|
|
62
|
+
purpose: 'any maskable'
|
|
63
|
+
}]
|
|
64
|
+
},
|
|
65
|
+
workbox: {
|
|
66
|
+
navigateFallback: '/',
|
|
67
|
+
globPatterns: ['**/*.{css,js,html,svg,png,ico,txt}']
|
|
68
|
+
},
|
|
69
|
+
devOptions: {
|
|
70
|
+
enabled: false,
|
|
71
|
+
navigateFallbackAllowlist: [/^\//]
|
|
72
|
+
},
|
|
73
|
+
experimental: {
|
|
74
|
+
directoryAndTrailingSlashHandler: true
|
|
75
|
+
}
|
|
76
|
+
}),
|
|
77
|
+
UnoCSS({
|
|
78
|
+
injectReset: true,
|
|
79
|
+
...unoConfig
|
|
80
|
+
}),
|
|
81
|
+
icon(iconConfig),
|
|
82
|
+
metaTags(),
|
|
83
|
+
(await import("@playform/inline")).default(),
|
|
84
|
+
pagefind(),
|
|
85
|
+
sitemap()
|
|
86
|
+
]
|
|
89
87
|
});
|
package/index.ts
CHANGED
|
@@ -38,6 +38,7 @@ export { default as Copyright } from './src/components/Copyright.astro';
|
|
|
38
38
|
export { default as HandDrive } from './src/components/HandDrive.astro';
|
|
39
39
|
export { default as Faq } from './src/components/Faq.astro';
|
|
40
40
|
export { default as FaqItem } from './src/components/FaqItem.astro';
|
|
41
|
+
export { default as ButtonCopy } from './src/components/ButtonCopy.astro'; // Add this line
|
|
41
42
|
|
|
42
43
|
export { default as ProductImage } from './src/components/Product/ProductImage.astro';
|
|
43
44
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spoko-design-system",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"main": "./index.ts",
|
|
6
6
|
"module": "./index.ts",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"@iconify/vue": "^4.3.0",
|
|
75
75
|
"@playform/compress": "^0.1.7",
|
|
76
76
|
"@playform/inline": "^0.1.1",
|
|
77
|
-
"@types/node": "^22.13.
|
|
77
|
+
"@types/node": "^22.13.4",
|
|
78
78
|
"@unocss/astro": "^65.4.3",
|
|
79
79
|
"@unocss/preset-attributify": "^65.4.3",
|
|
80
80
|
"@unocss/preset-typography": "^65.4.3",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"@unocss/preset-wind": "^65.4.3",
|
|
84
84
|
"@unocss/reset": "^65.4.3",
|
|
85
85
|
"@vite-pwa/astro": "^0.5.0",
|
|
86
|
-
"@vueuse/core": "^12.
|
|
86
|
+
"@vueuse/core": "^12.6.1",
|
|
87
87
|
"astro-i18next": "1.0.0-beta.21",
|
|
88
88
|
"astro-icon": "^1.1.5",
|
|
89
89
|
"astro-meta-tags": "^0.3.1",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"astro-remote": "^0.3.3",
|
|
93
93
|
"dotenv": "^16.4.7",
|
|
94
94
|
"i18next": "^24.2.2",
|
|
95
|
-
"i18next-browser-languagedetector": "^8.0.
|
|
95
|
+
"i18next-browser-languagedetector": "^8.0.3",
|
|
96
96
|
"i18next-fs-backend": "^2.6.0",
|
|
97
97
|
"i18next-http-backend": "^3.0.2",
|
|
98
98
|
"i18next-vue": "^5.1.0",
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
productNumber: string;
|
|
4
|
+
tooltipClasses?: string;
|
|
5
|
+
texts: {
|
|
6
|
+
copy: string;
|
|
7
|
+
copied: string;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
productNumber,
|
|
13
|
+
tooltipClasses = '',
|
|
14
|
+
texts
|
|
15
|
+
} = Astro.props;
|
|
16
|
+
|
|
17
|
+
// Move SVG definition to a constant to avoid recomputing
|
|
18
|
+
const COPY_ICON = `url("data:image/svg+xml;utf8,${encodeURIComponent(
|
|
19
|
+
`<svg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'><path fill='currentColor' d='M184 66H40a6 6 0 0 0-6 6v144a6 6 0 0 0 6 6h144a6 6 0 0 0 6-6V72a6 6 0 0 0-6-6m-6 144H46V78h132Zm44-170v144a6 6 0 0 1-12 0V46H72a6 6 0 0 1 0-12h144a6 6 0 0 1 6 6'/></svg>`
|
|
20
|
+
)}")`;
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
<button
|
|
24
|
+
aria-label={texts.copy}
|
|
25
|
+
class="btn-copy has-tooltip"
|
|
26
|
+
data-copy-text={productNumber}
|
|
27
|
+
>
|
|
28
|
+
<span
|
|
29
|
+
class:list={["tooltip rounded-full btn-copy-text", tooltipClasses]}
|
|
30
|
+
data-text={texts.copy}
|
|
31
|
+
data-copied-text={texts.copied}
|
|
32
|
+
/>
|
|
33
|
+
<span class="copy-icon" role="img" aria-hidden="true" />
|
|
34
|
+
</button>
|
|
35
|
+
|
|
36
|
+
<script>
|
|
37
|
+
class ClipboardButton {
|
|
38
|
+
private static COPY_TIMEOUT = 2000;
|
|
39
|
+
|
|
40
|
+
constructor(private button: HTMLButtonElement) {
|
|
41
|
+
this.init();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private init(): void {
|
|
45
|
+
this.button.addEventListener('click', () => this.handleCopy());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private get tooltip(): HTMLSpanElement {
|
|
49
|
+
return this.button.querySelector('.tooltip') as HTMLSpanElement;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private get copyText(): string {
|
|
53
|
+
return this.button.dataset.copyText || '';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private get originalText(): string {
|
|
57
|
+
return this.tooltip.dataset.text || 'Copy';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private get copiedText(): string {
|
|
61
|
+
return this.tooltip.dataset.copiedText || 'Copied';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private async handleCopy(): Promise<void> {
|
|
65
|
+
try {
|
|
66
|
+
await this.copyToClipboard();
|
|
67
|
+
this.updateTooltip();
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error('Failed to copy text:', err);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private async copyToClipboard(): Promise<void> {
|
|
74
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
75
|
+
await navigator.clipboard.writeText(this.copyText);
|
|
76
|
+
} else {
|
|
77
|
+
this.fallbackCopy();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private updateTooltip(): void {
|
|
82
|
+
this.tooltip.dataset.text = this.copiedText;
|
|
83
|
+
setTimeout(() => {
|
|
84
|
+
this.tooltip.dataset.text = this.originalText;
|
|
85
|
+
}, ClipboardButton.COPY_TIMEOUT);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private fallbackCopy(): void {
|
|
89
|
+
const textArea = document.createElement('textarea');
|
|
90
|
+
textArea.value = this.copyText;
|
|
91
|
+
textArea.style.position = 'fixed';
|
|
92
|
+
textArea.style.opacity = '0';
|
|
93
|
+
|
|
94
|
+
document.body.appendChild(textArea);
|
|
95
|
+
textArea.select();
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
document.execCommand('copy');
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.error('Fallback copy failed:', err);
|
|
101
|
+
} finally {
|
|
102
|
+
document.body.removeChild(textArea);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Use a single event listener with event delegation
|
|
108
|
+
document.addEventListener('astro:page-load', () => {
|
|
109
|
+
document.querySelectorAll('.btn-copy').forEach(button => {
|
|
110
|
+
if (button instanceof HTMLButtonElement) {
|
|
111
|
+
new ClipboardButton(button);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
</script>
|
|
116
|
+
|
|
117
|
+
<style define:vars={{ iconUrl: COPY_ICON }}>
|
|
118
|
+
.btn-copy {
|
|
119
|
+
position: absolute;
|
|
120
|
+
right: -1.75rem;
|
|
121
|
+
margin-left: auto;
|
|
122
|
+
width: 1.5rem;
|
|
123
|
+
height: 1.5rem;
|
|
124
|
+
line-height: 1;
|
|
125
|
+
opacity: 0.1;
|
|
126
|
+
display: inline-flex;
|
|
127
|
+
align-items: center;
|
|
128
|
+
justify-content: center;
|
|
129
|
+
transition: opacity 0.2s ease-in-out;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.btn-copy:hover {
|
|
133
|
+
opacity: 1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@media (min-width: 640px) {
|
|
137
|
+
.btn-copy {
|
|
138
|
+
right: -1.25rem;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.tooltip {
|
|
143
|
+
@apply invisible absolute -top-8 left-1/2 -translate-x-1/2 bg-neutral px-2 py-1 text-xs text-white opacity-0 transition-opacity;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.tooltip::before {
|
|
147
|
+
content: attr(data-text);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.has-tooltip:hover .tooltip {
|
|
151
|
+
@apply visible opacity-100;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.copy-icon {
|
|
155
|
+
width: 1.2em;
|
|
156
|
+
height: 1.2em;
|
|
157
|
+
display: inline-block;
|
|
158
|
+
vertical-align: middle;
|
|
159
|
+
background-color: currentColor;
|
|
160
|
+
-webkit-mask: var(--iconUrl) no-repeat;
|
|
161
|
+
mask: var(--iconUrl) no-repeat;
|
|
162
|
+
-webkit-mask-size: 100% 100%;
|
|
163
|
+
mask-size: 100% 100%;
|
|
164
|
+
}
|
|
165
|
+
</style>
|
|
@@ -4,11 +4,18 @@ import CategoryViewToggler from './CategoryViewToggler.astro';
|
|
|
4
4
|
import { Icon } from 'astro-icon/components';
|
|
5
5
|
import { t } from "i18next";
|
|
6
6
|
|
|
7
|
-
const {
|
|
7
|
+
const {
|
|
8
|
+
category,
|
|
9
|
+
subcategory,
|
|
10
|
+
subtitle,
|
|
11
|
+
subsubtitle,
|
|
12
|
+
titleSmall,
|
|
13
|
+
locale,
|
|
14
|
+
showViewToggler,
|
|
15
|
+
viewerLabels
|
|
16
|
+
} = Astro.props;
|
|
8
17
|
|
|
9
|
-
// Compute base URL for localization
|
|
10
18
|
const baseURL = locale === 'en' ? '' : `/${locale}`;
|
|
11
|
-
|
|
12
19
|
---
|
|
13
20
|
|
|
14
21
|
<div
|
|
@@ -16,26 +23,26 @@ const baseURL = locale === 'en' ? '' : `/${locale}`;
|
|
|
16
23
|
transition:name="category-details"
|
|
17
24
|
transition:animate="fade"
|
|
18
25
|
>
|
|
19
|
-
<CategorySidebarToggler onclick="toggleSidebar()">
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
</CategorySidebarToggler>
|
|
26
|
+
<CategorySidebarToggler onclick="toggleSidebar()">
|
|
27
|
+
<!-- Desktop expanded -->
|
|
28
|
+
<Icon
|
|
29
|
+
name="ant-design:menu-fold-outlined"
|
|
30
|
+
class="toggler-btn md:[&:not(.hidden)]:block"
|
|
31
|
+
aria-hidden="true"
|
|
32
|
+
/>
|
|
33
|
+
<!-- Desktop collapsed -->
|
|
34
|
+
<Icon
|
|
35
|
+
name="ant-design:menu-unfold-outlined"
|
|
36
|
+
class="toggler-btn hidden md:[&:not(.hidden)]:block"
|
|
37
|
+
aria-hidden="true"
|
|
38
|
+
/>
|
|
39
|
+
<!-- Mobile icon -->
|
|
40
|
+
<Icon
|
|
41
|
+
name="ant-design:menu-outlined"
|
|
42
|
+
class="toggler-btn block md:hidden"
|
|
43
|
+
aria-hidden="true"
|
|
44
|
+
/>
|
|
45
|
+
</CategorySidebarToggler>
|
|
39
46
|
|
|
40
47
|
<div class="overflow-x-auto overflow-y-hidden flex max-w-full items-center">
|
|
41
48
|
{subtitle ? (
|
|
@@ -51,7 +58,7 @@ const baseURL = locale === 'en' ? '' : `/${locale}`;
|
|
|
51
58
|
</h1>
|
|
52
59
|
) : (
|
|
53
60
|
<>
|
|
54
|
-
<div class="text-lg py-2.5 sm:py-0 whitespace-nowrap
|
|
61
|
+
<div class="text-lg py-2.5 sm:py-0 whitespace-nowrap">
|
|
55
62
|
<a href={`${baseURL}/${category.slug}/${subcategory.slug}/`}>
|
|
56
63
|
{subtitle}
|
|
57
64
|
</a>
|
|
@@ -66,36 +73,41 @@ const baseURL = locale === 'en' ? '' : `/${locale}`;
|
|
|
66
73
|
) : (
|
|
67
74
|
<h1 class="text-lg py-2.5 sm:py-0 whitespace-nowrap">
|
|
68
75
|
{category.name}
|
|
69
|
-
{titleSmall &&
|
|
76
|
+
{titleSmall && <small>{titleSmall}</small>}
|
|
70
77
|
<span class="sr-only"> {t('catalog.extra-short')}</span>
|
|
71
78
|
</h1>
|
|
72
79
|
)}
|
|
73
80
|
</div>
|
|
74
81
|
|
|
75
|
-
{
|
|
76
|
-
|
|
82
|
+
{showViewToggler && (
|
|
83
|
+
<CategoryViewToggler
|
|
84
|
+
{...viewerLabels}
|
|
85
|
+
class="hidden lg:flex items-center gap-2 ml-auto"
|
|
86
|
+
/>
|
|
87
|
+
)}
|
|
77
88
|
</div>
|
|
78
89
|
|
|
79
|
-
<script
|
|
90
|
+
<script>
|
|
80
91
|
function initializeSidebar() {
|
|
81
92
|
const sidebar = document.getElementById('sidebar');
|
|
82
93
|
const savedState = localStorage.getItem('sidebarState') || 'open';
|
|
83
94
|
|
|
84
95
|
if (sidebar) {
|
|
85
96
|
sidebar.classList.toggle('collapsed', savedState === 'closed');
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
updateTogglers(savedState === 'closed');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function updateTogglers(isCollapsed) {
|
|
102
|
+
const togglers = document.querySelectorAll('.toggler-btn');
|
|
103
|
+
if (togglers.length >= 2) {
|
|
104
|
+
togglers[0].classList.toggle('hidden', isCollapsed);
|
|
105
|
+
togglers[1].classList.toggle('hidden', !isCollapsed);
|
|
93
106
|
}
|
|
94
107
|
}
|
|
95
108
|
|
|
96
109
|
function toggleSidebar() {
|
|
97
110
|
const sidebar = document.getElementById('sidebar');
|
|
98
|
-
const togglers = document.querySelectorAll('.toggler-btn');
|
|
99
111
|
const isMobile = window.matchMedia("(max-width: 768px)").matches;
|
|
100
112
|
|
|
101
113
|
if (sidebar) {
|
|
@@ -106,21 +118,34 @@ const baseURL = locale === 'en' ? '' : `/${locale}`;
|
|
|
106
118
|
const isCollapsed = sidebar.classList.toggle('collapsed');
|
|
107
119
|
document.body.classList.remove('overflow-hidden');
|
|
108
120
|
localStorage.setItem('sidebarState', isCollapsed ? 'closed' : 'open');
|
|
109
|
-
|
|
110
|
-
if (togglers.length >= 2) {
|
|
111
|
-
togglers[0].classList.toggle('hidden', isCollapsed);
|
|
112
|
-
togglers[1].classList.toggle('hidden', !isCollapsed);
|
|
113
|
-
}
|
|
121
|
+
updateTogglers(isCollapsed);
|
|
114
122
|
}
|
|
115
123
|
}
|
|
116
124
|
}
|
|
117
|
-
|
|
118
|
-
document.addEventListener('DOMContentLoaded', initializeSidebar);
|
|
119
|
-
document.addEventListener('astro:page-load', initializeSidebar, { once: true });
|
|
120
|
-
</script>
|
|
121
125
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
// Initialize on astro page load
|
|
127
|
+
document.addEventListener('astro:page-load', initializeSidebar);
|
|
128
|
+
|
|
129
|
+
// Preserve state during view transitions
|
|
130
|
+
document.addEventListener('astro:before-swap', () => {
|
|
131
|
+
const sidebarState = localStorage.getItem('sidebarState');
|
|
132
|
+
if (sidebarState) {
|
|
133
|
+
sessionStorage.setItem('tempSidebarState', sidebarState);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
document.addEventListener('astro:after-swap', () => {
|
|
138
|
+
const tempState = sessionStorage.getItem('tempSidebarState');
|
|
139
|
+
if (tempState) {
|
|
140
|
+
localStorage.setItem('sidebarState', tempState);
|
|
141
|
+
sessionStorage.removeItem('tempSidebarState');
|
|
142
|
+
initializeSidebar();
|
|
125
143
|
}
|
|
126
|
-
|
|
144
|
+
});
|
|
145
|
+
</script>
|
|
146
|
+
|
|
147
|
+
<style>
|
|
148
|
+
.toggler-btn {
|
|
149
|
+
@apply md:-mt-0.5;
|
|
150
|
+
}
|
|
151
|
+
</style>
|
|
@@ -42,35 +42,41 @@ const {
|
|
|
42
42
|
</div>
|
|
43
43
|
</div>
|
|
44
44
|
|
|
45
|
+
<script>
|
|
46
|
+
function initializeView() {
|
|
47
|
+
const savedView = localStorage.getItem('categoryView') || 'list';
|
|
48
|
+
updateUI(savedView);
|
|
49
|
+
|
|
50
|
+
// Clean up existing listeners to prevent duplicates
|
|
51
|
+
document.querySelectorAll('.view-toggle').forEach(btn => {
|
|
52
|
+
btn.removeEventListener('click', handleViewToggle);
|
|
53
|
+
btn.addEventListener('click', handleViewToggle);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
45
56
|
|
|
46
|
-
<script >
|
|
47
57
|
function handleViewToggle(e) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const view = button.dataset.view;
|
|
58
|
+
if (!(e.currentTarget instanceof HTMLElement)) return;
|
|
59
|
+
const view = e.currentTarget.dataset.view;
|
|
52
60
|
if (!view) return;
|
|
53
|
-
|
|
61
|
+
|
|
54
62
|
localStorage.setItem('categoryView', view);
|
|
55
63
|
updateUI(view);
|
|
56
64
|
}
|
|
57
|
-
|
|
65
|
+
|
|
58
66
|
function updateUI(view) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
// Update toggle buttons
|
|
68
|
+
document.querySelectorAll('.view-toggle').forEach(btn => {
|
|
69
|
+
btn.classList.toggle('bg-neutral-lightest', btn.dataset.view === view);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Update products container
|
|
63
73
|
const productsContainer = document.querySelector('.products-container');
|
|
64
74
|
if (productsContainer) {
|
|
65
75
|
productsContainer.classList.remove('view-grid', 'view-list');
|
|
66
76
|
productsContainer.classList.add(`view-${view}`);
|
|
67
77
|
}
|
|
68
78
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
document.querySelectorAll('.view-toggle')
|
|
74
|
-
.forEach(btn => btn.addEventListener('click', handleViewToggle));
|
|
75
|
-
}, { once: true });
|
|
76
|
-
</script>
|
|
79
|
+
|
|
80
|
+
// Initialize on page load and view transitions
|
|
81
|
+
document.addEventListener('astro:page-load', initializeView);
|
|
82
|
+
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import ButtonCopy from '../ButtonCopy.
|
|
2
|
+
import ButtonCopy from '../ButtonCopy.astro';
|
|
3
3
|
import useFormatProductNumber from '../../utils/product/useFormatProductNumber';
|
|
4
4
|
|
|
5
5
|
const {
|
|
@@ -41,7 +41,6 @@ const FormattedWrapper = isPdp ? 'h3' : 'div';
|
|
|
41
41
|
productNumber={productNumber}
|
|
42
42
|
texts={buttonTexts}
|
|
43
43
|
tooltipClasses=""
|
|
44
|
-
client:only="vue"
|
|
45
44
|
/>
|
|
46
45
|
)}
|
|
47
46
|
</div>
|
|
@@ -25,7 +25,7 @@ export const jumbotronShortcuts = [
|
|
|
25
25
|
|
|
26
26
|
// Metadata and categories
|
|
27
27
|
['jumbotron-meta', 'order-3 flex items-center text-gray-100'],
|
|
28
|
-
['jumbotron-categories', 'order-1'],
|
|
28
|
+
['jumbotron-categories', 'order-1 mt-4'],
|
|
29
29
|
|
|
30
30
|
// Content styles
|
|
31
31
|
['jumbotron-description', 'mb-1 line-clamp-3 text-base sm:text-lg leading-none mt-4'],
|