@latte-macchiat-io/latte-vanilla-components 0.0.201 → 0.0.203
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/package.json +1 -1
- package/src/components/Actions/index.tsx +3 -6
- package/src/components/Actions/stories.tsx +7 -1
- package/src/components/Button/export.tsx +2 -5
- package/src/components/Button/index.tsx +19 -14
- package/src/components/Button/stories.tsx +12 -219
- package/src/components/Button/theme.ts +149 -0
- package/src/components/Carousel/export.tsx +2 -4
- package/src/components/Carousel/index.tsx +159 -191
- package/src/components/Carousel/theme.ts +102 -0
- package/src/components/Columns/export.tsx +2 -5
- package/src/components/Columns/index.tsx +10 -15
- package/src/components/Columns/styles.css.ts +3 -1
- package/src/components/Columns/theme.ts +24 -0
- package/src/components/ConsentCookie/export.tsx +2 -4
- package/src/components/ConsentCookie/index.tsx +104 -0
- package/src/components/Footer/export.tsx +1 -4
- package/src/components/Footer/index.tsx +1 -8
- package/src/components/Footer/theme.ts +51 -0
- package/src/components/Form/export.tsx +17 -3
- package/src/components/Header/export.tsx +1 -4
- package/src/components/Header/index.tsx +19 -35
- package/src/components/Header/styles.css.ts +2 -2
- package/src/components/Header/theme.ts +51 -0
- package/src/components/Heading/export.tsx +1 -0
- package/src/components/Heading/index.tsx +3 -8
- package/src/components/Icon/export.tsx +1 -4
- package/src/components/Icon/index.tsx +4 -5
- package/src/components/Icon/theme.ts +17 -0
- package/src/components/KeyNumber/export.tsx +2 -4
- package/src/components/KeyNumber/index.tsx +3 -3
- package/src/components/KeyNumber/theme.ts +22 -0
- package/src/components/LanguageSwitcher/export.tsx +2 -4
- package/src/components/LanguageSwitcher/index.tsx +37 -63
- package/src/components/Logo/export.tsx +1 -4
- package/src/components/Logo/index.tsx +2 -5
- package/src/components/Logo/styles.css.ts +2 -2
- package/src/components/Main/export.tsx +1 -4
- package/src/components/Main/index.tsx +1 -9
- package/src/components/Main/theme.ts +19 -0
- package/src/components/Modal/export.tsx +2 -4
- package/src/components/Modal/index.tsx +4 -5
- package/src/components/Modal/theme.ts +31 -0
- package/src/components/Nav/export.tsx +1 -4
- package/src/components/Nav/index.tsx +6 -14
- package/src/components/Nav/styles.css.ts +3 -1
- package/src/components/Nav/theme.ts +24 -0
- package/src/components/NavLegal/export.tsx +1 -4
- package/src/components/NavLegal/index.tsx +1 -7
- package/src/components/NavLegal/theme.ts +24 -0
- package/src/components/NavSocial/export.tsx +2 -5
- package/src/components/NavSocial/index.tsx +14 -18
- package/src/components/NavSocial/theme.ts +34 -0
- package/src/components/Section/export.tsx +2 -6
- package/src/components/Section/index.tsx +4 -8
- package/src/components/Section/theme.ts +40 -0
- package/src/components/Video/export.tsx +2 -2
- package/src/components/Video/index.tsx +78 -83
- package/src/components/Video/theme.ts +104 -0
- package/src/index.ts +19 -51
- package/src/theme/baseThemeValues.ts +46 -965
- package/src/theme/contract.css.ts +0 -16
- package/src/utils/useWindowSize.ts +29 -0
- package/src/components/ConsentCookie/ConsentCookie.tsx +0 -200
- package/src/components/Header/HeaderOverlay/index.tsx +0 -32
- package/src/components/Header/HeaderOverlay/styles.css.ts +0 -33
- package/src/components/Header/ToggleNav/index.tsx +0 -29
- package/src/components/Header/ToggleNav/styles.css.ts +0 -40
- package/src/components/ThemeTest/ThemeTest.css.ts +0 -11
- package/src/components/ThemeTest/ThemeTest.tsx +0 -12
- /package/src/components/ConsentCookie/{ConsentCookie.css.ts → styles.css.ts} +0 -0
@@ -1,6 +1,5 @@
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2
1
|
import { clsx } from 'clsx';
|
3
|
-
import {
|
2
|
+
import { ReactNode, useEffect, useRef, useState } from 'react';
|
4
3
|
|
5
4
|
import {
|
6
5
|
carouselBullet,
|
@@ -16,214 +15,183 @@ import {
|
|
16
15
|
} from './styles.css';
|
17
16
|
|
18
17
|
import { breakpoints } from '../../styles/mediaqueries';
|
19
|
-
import { Icon } from '../Icon';
|
20
18
|
|
21
|
-
|
22
|
-
width: number | undefined;
|
23
|
-
height: number | undefined;
|
24
|
-
}
|
19
|
+
import { useWindowSize } from '../../utils/useWindowSize';
|
25
20
|
|
26
|
-
|
27
|
-
const [windowSize, setWindowSize] = useState<UseWindowSizeReturn>({
|
28
|
-
width: undefined,
|
29
|
-
height: undefined,
|
30
|
-
});
|
21
|
+
import { Icon } from '../Icon';
|
31
22
|
|
23
|
+
export type CarouselProps = React.HTMLAttributes<HTMLDivElement> &
|
24
|
+
CarouselVariants & {
|
25
|
+
gap?: number;
|
26
|
+
data: ReactNode[];
|
27
|
+
itemsPerView?: number;
|
28
|
+
showBullets?: boolean;
|
29
|
+
showNavButtons?: boolean;
|
30
|
+
|
31
|
+
autoplay?: boolean;
|
32
|
+
autoplayInterval?: number;
|
33
|
+
};
|
34
|
+
|
35
|
+
export const Carousel = ({
|
36
|
+
data,
|
37
|
+
itemsPerView = 1,
|
38
|
+
showNavButtons = false,
|
39
|
+
showBullets = false,
|
40
|
+
autoplay = false,
|
41
|
+
autoplayInterval = 3000,
|
42
|
+
gap = 16,
|
43
|
+
isFullWidth,
|
44
|
+
className,
|
45
|
+
}: CarouselProps) => {
|
46
|
+
const { width: windowWidth } = useWindowSize();
|
47
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
48
|
+
const [itemWidth, setItemWidth] = useState(0);
|
49
|
+
const [visibleItems, setVisibleItems] = useState(itemsPerView);
|
50
|
+
|
51
|
+
const carouselRef = useRef<HTMLDivElement>(null);
|
52
|
+
const slideRef = useRef<HTMLDivElement>(null);
|
53
|
+
|
54
|
+
const isTablet = windowWidth !== undefined && windowWidth > breakpoints.md;
|
55
|
+
const isDesktop = windowWidth !== undefined && windowWidth > breakpoints.lg;
|
56
|
+
|
57
|
+
// Adapter le nombre d’items visibles selon le viewport
|
32
58
|
useEffect(() => {
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
59
|
+
if (isDesktop) {
|
60
|
+
setVisibleItems(itemsPerView);
|
61
|
+
} else if (isTablet) {
|
62
|
+
setVisibleItems(Math.max(1, Math.floor(itemsPerView / 2)));
|
63
|
+
} else {
|
64
|
+
setVisibleItems(1);
|
65
|
+
}
|
66
|
+
}, [isTablet, isDesktop, itemsPerView]);
|
67
|
+
|
68
|
+
// Calcul largeur d’un item
|
69
|
+
useEffect(() => {
|
70
|
+
const calculateItemWidth = () => {
|
71
|
+
if (carouselRef.current) {
|
72
|
+
const containerWidth = carouselRef.current.getBoundingClientRect().width;
|
73
|
+
const totalGap = (visibleItems - 1) * gap;
|
74
|
+
setItemWidth((containerWidth - totalGap) / visibleItems);
|
75
|
+
}
|
38
76
|
};
|
39
77
|
|
40
|
-
|
41
|
-
|
78
|
+
calculateItemWidth();
|
79
|
+
window.addEventListener('resize', calculateItemWidth);
|
80
|
+
return () => window.removeEventListener('resize', calculateItemWidth);
|
81
|
+
}, [visibleItems, gap]);
|
42
82
|
|
43
|
-
|
44
|
-
|
83
|
+
// Autoplay
|
84
|
+
useEffect(() => {
|
85
|
+
if (!autoplay) return;
|
45
86
|
|
46
|
-
|
47
|
-
|
87
|
+
const interval = setInterval(() => {
|
88
|
+
setCurrentIndex((prev) => {
|
89
|
+
const maxIndex = Math.max(0, data.length - visibleItems);
|
90
|
+
return prev >= maxIndex ? 0 : prev + 1;
|
91
|
+
});
|
92
|
+
}, autoplayInterval);
|
48
93
|
|
49
|
-
|
50
|
-
|
51
|
-
data: ReactNode[];
|
52
|
-
itemsPerView?: number;
|
53
|
-
showNavButtons?: boolean;
|
54
|
-
showBullets?: boolean;
|
55
|
-
autoplay?: boolean;
|
56
|
-
autoplayInterval?: number;
|
57
|
-
gap?: number; // simple px value
|
58
|
-
}
|
59
|
-
|
60
|
-
export const Carousel = forwardRef<HTMLDivElement, CarouselProps>(
|
61
|
-
(
|
62
|
-
{
|
63
|
-
data,
|
64
|
-
itemsPerView = 1,
|
65
|
-
showNavButtons = false,
|
66
|
-
showBullets = false,
|
67
|
-
autoplay = false,
|
68
|
-
autoplayInterval = 3000,
|
69
|
-
gap = 16,
|
70
|
-
isFullWidth,
|
71
|
-
css,
|
72
|
-
className,
|
73
|
-
...htmlProps
|
74
|
-
},
|
75
|
-
ref
|
76
|
-
) => {
|
77
|
-
const { width: windowWidth } = useWindowSize();
|
78
|
-
const [currentIndex, setCurrentIndex] = useState(0);
|
79
|
-
const [itemWidth, setItemWidth] = useState(0);
|
80
|
-
const [visibleItems, setVisibleItems] = useState(itemsPerView);
|
81
|
-
|
82
|
-
const carouselRef = useRef<HTMLDivElement>(null);
|
83
|
-
const slideRef = useRef<HTMLDivElement>(null);
|
84
|
-
|
85
|
-
const isTablet = windowWidth !== undefined && windowWidth > breakpoints.md;
|
86
|
-
const isDesktop = windowWidth !== undefined && windowWidth > breakpoints.lg;
|
87
|
-
|
88
|
-
// 🔹 Adapter le nombre d’items visibles selon le viewport
|
89
|
-
useEffect(() => {
|
90
|
-
if (isDesktop) {
|
91
|
-
setVisibleItems(itemsPerView);
|
92
|
-
} else if (isTablet) {
|
93
|
-
setVisibleItems(Math.max(1, Math.floor(itemsPerView / 2)));
|
94
|
-
} else {
|
95
|
-
setVisibleItems(1);
|
96
|
-
}
|
97
|
-
}, [isTablet, isDesktop, itemsPerView]);
|
98
|
-
|
99
|
-
// 🔹 Calcul largeur d’un item
|
100
|
-
useEffect(() => {
|
101
|
-
const calculateItemWidth = () => {
|
102
|
-
if (carouselRef.current) {
|
103
|
-
const containerWidth = carouselRef.current.getBoundingClientRect().width;
|
104
|
-
const totalGap = (visibleItems - 1) * gap;
|
105
|
-
setItemWidth((containerWidth - totalGap) / visibleItems);
|
106
|
-
}
|
107
|
-
};
|
108
|
-
|
109
|
-
calculateItemWidth();
|
110
|
-
window.addEventListener('resize', calculateItemWidth);
|
111
|
-
return () => window.removeEventListener('resize', calculateItemWidth);
|
112
|
-
}, [visibleItems, gap]);
|
113
|
-
|
114
|
-
// 🔹 Autoplay
|
115
|
-
useEffect(() => {
|
116
|
-
if (!autoplay) return;
|
117
|
-
|
118
|
-
const interval = setInterval(() => {
|
119
|
-
setCurrentIndex((prev) => {
|
120
|
-
const maxIndex = Math.max(0, data.length - visibleItems);
|
121
|
-
return prev >= maxIndex ? 0 : prev + 1;
|
122
|
-
});
|
123
|
-
}, autoplayInterval);
|
124
|
-
|
125
|
-
return () => clearInterval(interval);
|
126
|
-
}, [autoplay, autoplayInterval, data.length, visibleItems]);
|
127
|
-
|
128
|
-
// 🔹 Swipe mobile
|
129
|
-
useEffect(() => {
|
130
|
-
const carousel = carouselRef.current;
|
131
|
-
if (!carousel) return;
|
132
|
-
|
133
|
-
let touchStartX = 0;
|
134
|
-
let touchEndX = 0;
|
135
|
-
|
136
|
-
const handleTouchStart = (e: TouchEvent) => {
|
137
|
-
touchStartX = e.changedTouches[0].screenX;
|
138
|
-
};
|
139
|
-
|
140
|
-
const handleTouchEnd = (e: TouchEvent) => {
|
141
|
-
touchEndX = e.changedTouches[0].screenX;
|
142
|
-
handleSwipe();
|
143
|
-
};
|
144
|
-
|
145
|
-
const handleSwipe = () => {
|
146
|
-
const swipeThreshold = 100;
|
147
|
-
const diff = touchStartX - touchEndX;
|
148
|
-
|
149
|
-
if (Math.abs(diff) > swipeThreshold) {
|
150
|
-
if (diff > 0) {
|
151
|
-
handleNext();
|
152
|
-
} else {
|
153
|
-
handlePrevious();
|
154
|
-
}
|
155
|
-
}
|
156
|
-
};
|
94
|
+
return () => clearInterval(interval);
|
95
|
+
}, [autoplay, autoplayInterval, data.length, visibleItems]);
|
157
96
|
|
158
|
-
|
159
|
-
|
97
|
+
// Swipe mobile
|
98
|
+
useEffect(() => {
|
99
|
+
const carousel = carouselRef.current;
|
100
|
+
if (!carousel) return;
|
160
101
|
|
161
|
-
|
162
|
-
|
163
|
-
carousel.removeEventListener('touchend', handleTouchEnd);
|
164
|
-
};
|
165
|
-
}, []);
|
102
|
+
let touchStartX = 0;
|
103
|
+
let touchEndX = 0;
|
166
104
|
|
167
|
-
const
|
168
|
-
|
105
|
+
const handleTouchStart = (e: TouchEvent) => {
|
106
|
+
touchStartX = e.changedTouches[0].screenX;
|
169
107
|
};
|
170
108
|
|
171
|
-
const
|
172
|
-
|
173
|
-
|
109
|
+
const handleTouchEnd = (e: TouchEvent) => {
|
110
|
+
touchEndX = e.changedTouches[0].screenX;
|
111
|
+
handleSwipe();
|
174
112
|
};
|
175
113
|
|
176
|
-
const
|
177
|
-
const
|
178
|
-
|
114
|
+
const handleSwipe = () => {
|
115
|
+
const swipeThreshold = 100;
|
116
|
+
const diff = touchStartX - touchEndX;
|
117
|
+
|
118
|
+
if (Math.abs(diff) > swipeThreshold) {
|
119
|
+
if (diff > 0) {
|
120
|
+
handleNext();
|
121
|
+
} else {
|
122
|
+
handlePrevious();
|
123
|
+
}
|
124
|
+
}
|
179
125
|
};
|
180
126
|
|
181
|
-
|
127
|
+
carousel.addEventListener('touchstart', handleTouchStart, { passive: true });
|
128
|
+
carousel.addEventListener('touchend', handleTouchEnd, { passive: true });
|
129
|
+
|
130
|
+
return () => {
|
131
|
+
carousel.removeEventListener('touchstart', handleTouchStart);
|
132
|
+
carousel.removeEventListener('touchend', handleTouchEnd);
|
133
|
+
};
|
134
|
+
}, []);
|
135
|
+
|
136
|
+
const handlePrevious = () => {
|
137
|
+
setCurrentIndex((prev) => Math.max(0, prev - 1));
|
138
|
+
};
|
139
|
+
|
140
|
+
const handleNext = () => {
|
182
141
|
const maxIndex = Math.max(0, data.length - visibleItems);
|
142
|
+
setCurrentIndex((prev) => Math.min(maxIndex, prev + 1));
|
143
|
+
};
|
183
144
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
145
|
+
const handleBulletClick = (index: number) => {
|
146
|
+
const maxIndex = Math.max(0, data.length - visibleItems);
|
147
|
+
setCurrentIndex(Math.min(index, maxIndex));
|
148
|
+
};
|
149
|
+
|
150
|
+
const translateX = -(currentIndex * (itemWidth + gap));
|
151
|
+
const maxIndex = Math.max(0, data.length - visibleItems);
|
152
|
+
|
153
|
+
return (
|
154
|
+
<div className={clsx(carouselRecipe({ isFullWidth }), className)}>
|
155
|
+
<div ref={carouselRef} className={carouselContent}>
|
156
|
+
<div
|
157
|
+
ref={slideRef}
|
158
|
+
className={carouselSlide}
|
159
|
+
style={{
|
160
|
+
gap: `${gap}px`,
|
161
|
+
transform: `translateX(${translateX}px)`,
|
162
|
+
}}>
|
163
|
+
{data.map((item, index) => (
|
164
|
+
<div key={index} className={carouselItem} style={{ width: `${itemWidth}px` }}>
|
165
|
+
{item}
|
166
|
+
</div>
|
167
|
+
))}
|
200
168
|
</div>
|
201
|
-
|
202
|
-
{showNavButtons && (
|
203
|
-
<div className={carouselNav}>
|
204
|
-
<button type="button" className={carouselNavButton} onClick={handlePrevious} disabled={currentIndex === 0} aria-label="Previous slide">
|
205
|
-
<Icon icon="arrowBack" />
|
206
|
-
</button>
|
207
|
-
<button type="button" className={carouselNavButton} onClick={handleNext} disabled={currentIndex >= maxIndex} aria-label="Next slide">
|
208
|
-
<Icon icon="arrowForward" />
|
209
|
-
</button>
|
210
|
-
</div>
|
211
|
-
)}
|
212
|
-
|
213
|
-
{showBullets && (
|
214
|
-
<div className={carouselBullets}>
|
215
|
-
{Array.from({ length: maxIndex + 1 }, (_, index) => (
|
216
|
-
<button
|
217
|
-
key={index}
|
218
|
-
type="button"
|
219
|
-
aria-label={`Go to slide ${index + 1}`}
|
220
|
-
onClick={() => handleBulletClick(index)}
|
221
|
-
className={clsx(carouselBullet, index === currentIndex && carouselBulletActive)}
|
222
|
-
/>
|
223
|
-
))}
|
224
|
-
</div>
|
225
|
-
)}
|
226
169
|
</div>
|
227
|
-
|
228
|
-
|
229
|
-
|
170
|
+
|
171
|
+
{showNavButtons && (
|
172
|
+
<div className={carouselNav}>
|
173
|
+
<button type="button" className={carouselNavButton} onClick={handlePrevious} disabled={currentIndex === 0} aria-label="Previous slide">
|
174
|
+
<Icon icon="arrowBack" />
|
175
|
+
</button>
|
176
|
+
<button type="button" className={carouselNavButton} onClick={handleNext} disabled={currentIndex >= maxIndex} aria-label="Next slide">
|
177
|
+
<Icon icon="arrowForward" />
|
178
|
+
</button>
|
179
|
+
</div>
|
180
|
+
)}
|
181
|
+
|
182
|
+
{showBullets && (
|
183
|
+
<div className={carouselBullets}>
|
184
|
+
{Array.from({ length: maxIndex + 1 }, (_, index) => (
|
185
|
+
<button
|
186
|
+
key={index}
|
187
|
+
type="button"
|
188
|
+
aria-label={`Go to slide ${index + 1}`}
|
189
|
+
onClick={() => handleBulletClick(index)}
|
190
|
+
className={clsx(carouselBullet, index === currentIndex && carouselBulletActive)}
|
191
|
+
/>
|
192
|
+
))}
|
193
|
+
</div>
|
194
|
+
)}
|
195
|
+
</div>
|
196
|
+
);
|
197
|
+
};
|
@@ -0,0 +1,102 @@
|
|
1
|
+
const themeCarouselBase = {
|
2
|
+
carousel: {
|
3
|
+
gap: {
|
4
|
+
mobile: '15',
|
5
|
+
sm: '15',
|
6
|
+
md: '30',
|
7
|
+
lg: '30',
|
8
|
+
xl: '50',
|
9
|
+
'2xl': '50',
|
10
|
+
},
|
11
|
+
nav: {
|
12
|
+
borderRadius: '1000px',
|
13
|
+
width: {
|
14
|
+
mobile: '15',
|
15
|
+
sm: '15',
|
16
|
+
md: '30',
|
17
|
+
lg: '30',
|
18
|
+
xl: '50',
|
19
|
+
'2xl': '50',
|
20
|
+
},
|
21
|
+
height: {
|
22
|
+
mobile: '15',
|
23
|
+
sm: '15',
|
24
|
+
md: '30',
|
25
|
+
lg: '30',
|
26
|
+
xl: '50',
|
27
|
+
'2xl': '50',
|
28
|
+
},
|
29
|
+
},
|
30
|
+
bullet: {
|
31
|
+
borderRadius: '1000px',
|
32
|
+
bottom: {
|
33
|
+
mobile: '15px',
|
34
|
+
sm: '15px',
|
35
|
+
md: '30px',
|
36
|
+
lg: '30px',
|
37
|
+
xl: '50px',
|
38
|
+
'2xl': '50px',
|
39
|
+
},
|
40
|
+
gap: {
|
41
|
+
mobile: '15',
|
42
|
+
sm: '15',
|
43
|
+
md: '30',
|
44
|
+
lg: '30',
|
45
|
+
xl: '50',
|
46
|
+
'2xl': '50',
|
47
|
+
},
|
48
|
+
width: {
|
49
|
+
mobile: '15',
|
50
|
+
sm: '15',
|
51
|
+
md: '30',
|
52
|
+
lg: '30',
|
53
|
+
xl: '50',
|
54
|
+
'2xl': '50',
|
55
|
+
},
|
56
|
+
height: {
|
57
|
+
mobile: '15',
|
58
|
+
sm: '15',
|
59
|
+
md: '30',
|
60
|
+
lg: '30',
|
61
|
+
xl: '50',
|
62
|
+
'2xl': '50',
|
63
|
+
},
|
64
|
+
},
|
65
|
+
},
|
66
|
+
};
|
67
|
+
|
68
|
+
export const themeCarouselLight = {
|
69
|
+
carousel: {
|
70
|
+
...themeCarouselBase.carousel,
|
71
|
+
iconColor: '#FF7377',
|
72
|
+
|
73
|
+
nav: {
|
74
|
+
...themeCarouselBase.carousel.nav,
|
75
|
+
backgroundColor: '#FF7377',
|
76
|
+
},
|
77
|
+
|
78
|
+
bullet: {
|
79
|
+
...themeCarouselBase.carousel.bullet,
|
80
|
+
backgroundColor: '#FF7377',
|
81
|
+
activeBackgroundColor: '#FF7377',
|
82
|
+
},
|
83
|
+
},
|
84
|
+
};
|
85
|
+
|
86
|
+
export const themeCarouselDark = {
|
87
|
+
carousel: {
|
88
|
+
...themeCarouselBase.carousel,
|
89
|
+
iconColor: '#FF7377',
|
90
|
+
|
91
|
+
nav: {
|
92
|
+
...themeCarouselBase.carousel.nav,
|
93
|
+
backgroundColor: '#FF7377',
|
94
|
+
},
|
95
|
+
|
96
|
+
bullet: {
|
97
|
+
...themeCarouselBase.carousel.bullet,
|
98
|
+
backgroundColor: '#FF7377',
|
99
|
+
activeBackgroundColor: '#FF7377',
|
100
|
+
},
|
101
|
+
},
|
102
|
+
};
|
@@ -1,5 +1,2 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
// export { Align as ColumnsAlign } from './types';
|
4
|
-
|
5
|
-
// export { styles as ColumnsStyles } from './styles.css'
|
1
|
+
export { Columns, type ColumnsProps } from './';
|
2
|
+
export { type ColumnsVariants } from './styles.css';
|
@@ -1,22 +1,17 @@
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2
|
-
'use client';
|
3
|
-
|
4
1
|
import { clsx } from 'clsx';
|
5
|
-
import { forwardRef } from 'react';
|
6
2
|
|
7
|
-
import { columnItem, columnsRecipe } from './styles.css';
|
3
|
+
import { columnItem, columnsRecipe, ColumnsVariants } from './styles.css';
|
8
4
|
|
9
|
-
export type ColumnsProps = React.HTMLAttributes<HTMLDivElement> &
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
};
|
5
|
+
export type ColumnsProps = React.HTMLAttributes<HTMLDivElement> &
|
6
|
+
ColumnsVariants & {
|
7
|
+
css?: string;
|
8
|
+
columns: number[];
|
9
|
+
children: React.ReactNode;
|
10
|
+
};
|
16
11
|
|
17
|
-
export const Columns =
|
12
|
+
export const Columns = ({ align, vAlign, columns, children, className, css }: ColumnsProps) => {
|
18
13
|
return (
|
19
|
-
<div
|
14
|
+
<div className={clsx(columnsRecipe({ align, vAlign }), css, className)}>
|
20
15
|
{Array.isArray(children) &&
|
21
16
|
children.map((child, index) => (
|
22
17
|
<div
|
@@ -31,4 +26,4 @@ export const Columns = forwardRef<HTMLDivElement, ColumnsProps>(({ align, vAlign
|
|
31
26
|
))}
|
32
27
|
</div>
|
33
28
|
);
|
34
|
-
}
|
29
|
+
};
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { style } from '@vanilla-extract/css';
|
2
|
-
import { recipe } from '@vanilla-extract/recipes';
|
2
|
+
import { recipe, RecipeVariants } from '@vanilla-extract/recipes';
|
3
3
|
import { queries } from '../../styles/mediaqueries';
|
4
4
|
import { themeContract } from '../../theme/contract.css';
|
5
5
|
import { generateResponsiveMedia } from '../../utils/generateResponsiveMedia';
|
@@ -68,3 +68,5 @@ export const columnItem = style({
|
|
68
68
|
},
|
69
69
|
},
|
70
70
|
});
|
71
|
+
|
72
|
+
export type ColumnsVariants = RecipeVariants<typeof columnsRecipe>;
|
@@ -0,0 +1,24 @@
|
|
1
|
+
const themeColumnsBase = {
|
2
|
+
columns: {
|
3
|
+
gap: {
|
4
|
+
mobile: '15',
|
5
|
+
sm: '15',
|
6
|
+
md: '30',
|
7
|
+
lg: '30',
|
8
|
+
xl: '50',
|
9
|
+
'2xl': '50',
|
10
|
+
},
|
11
|
+
},
|
12
|
+
};
|
13
|
+
|
14
|
+
export const themeColumnsLight = {
|
15
|
+
columns: {
|
16
|
+
...themeColumnsBase.columns,
|
17
|
+
},
|
18
|
+
};
|
19
|
+
|
20
|
+
export const themeColumnsDark = {
|
21
|
+
columns: {
|
22
|
+
...themeColumnsBase.columns,
|
23
|
+
},
|
24
|
+
};
|
@@ -1,4 +1,2 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
// export { styles as ConsentCookieStyles } from './styles.css';
|
1
|
+
export { ConsentCookie, type ConsentCookieProps } from './';
|
2
|
+
export { type ConsentCookieVariants } from './styles.css';
|
@@ -0,0 +1,104 @@
|
|
1
|
+
import { clsx } from 'clsx';
|
2
|
+
import { useEffect, useState } from 'react';
|
3
|
+
|
4
|
+
import { getCookie, setCookie } from './cookie';
|
5
|
+
import { consentActions, consentContent, type ConsentCookieVariants, consentRecipe } from './styles.css';
|
6
|
+
|
7
|
+
import { Button } from '../Button';
|
8
|
+
|
9
|
+
// Declare window object including gtag to avoid TypeScript errors
|
10
|
+
declare const window: Window &
|
11
|
+
typeof globalThis & {
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
13
|
+
gtag: any;
|
14
|
+
};
|
15
|
+
|
16
|
+
export interface ConsentCookieProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'color'>, NonNullable<ConsentCookieVariants> {
|
17
|
+
cookieName?: string;
|
18
|
+
cookieExpirationDays?: number;
|
19
|
+
onAccept?: () => void;
|
20
|
+
onReject?: () => void;
|
21
|
+
translations?: {
|
22
|
+
actions: {
|
23
|
+
accept: string;
|
24
|
+
reject: string;
|
25
|
+
};
|
26
|
+
};
|
27
|
+
enableGoogleAnalytics?: boolean;
|
28
|
+
}
|
29
|
+
|
30
|
+
export const ConsentCookie = ({
|
31
|
+
variant,
|
32
|
+
onAccept,
|
33
|
+
onReject,
|
34
|
+
children,
|
35
|
+
className,
|
36
|
+
translations,
|
37
|
+
cookieName = 'consent',
|
38
|
+
cookieExpirationDays = 365,
|
39
|
+
enableGoogleAnalytics = true,
|
40
|
+
}: ConsentCookieProps) => {
|
41
|
+
const [showConsent, setShowConsent] = useState(false);
|
42
|
+
|
43
|
+
const handleAccept = () => {
|
44
|
+
setShowConsent(false);
|
45
|
+
setCookie(cookieName, 'true', cookieExpirationDays);
|
46
|
+
|
47
|
+
if (enableGoogleAnalytics && typeof window !== 'undefined' && typeof window.gtag !== 'undefined') {
|
48
|
+
window.gtag('consent', 'update', {
|
49
|
+
analytics_storage: 'granted',
|
50
|
+
});
|
51
|
+
}
|
52
|
+
|
53
|
+
onAccept?.();
|
54
|
+
};
|
55
|
+
|
56
|
+
const handleReject = () => {
|
57
|
+
setShowConsent(false);
|
58
|
+
setCookie(cookieName, 'false', cookieExpirationDays);
|
59
|
+
|
60
|
+
if (enableGoogleAnalytics && typeof window !== 'undefined' && typeof window.gtag !== 'undefined') {
|
61
|
+
window.gtag('consent', 'update', {
|
62
|
+
analytics_storage: 'denied',
|
63
|
+
});
|
64
|
+
}
|
65
|
+
|
66
|
+
onReject?.();
|
67
|
+
};
|
68
|
+
|
69
|
+
useEffect(() => {
|
70
|
+
const consentValue = getCookie(cookieName);
|
71
|
+
const shouldShowConsent = consentValue !== 'true' && consentValue !== 'false';
|
72
|
+
const areCookiesAccepted = consentValue === 'true';
|
73
|
+
|
74
|
+
if (shouldShowConsent) {
|
75
|
+
setShowConsent(true);
|
76
|
+
}
|
77
|
+
|
78
|
+
// Set initial Google Analytics consent
|
79
|
+
if (enableGoogleAnalytics && typeof window !== 'undefined' && typeof window.gtag !== 'undefined') {
|
80
|
+
const gAnalyticsConsent = areCookiesAccepted ? 'granted' : 'denied';
|
81
|
+
window.gtag('consent', 'update', {
|
82
|
+
analytics_storage: gAnalyticsConsent,
|
83
|
+
});
|
84
|
+
}
|
85
|
+
}, [cookieName, enableGoogleAnalytics]);
|
86
|
+
|
87
|
+
if (!showConsent) return null;
|
88
|
+
|
89
|
+
return (
|
90
|
+
<div className={clsx(consentRecipe({ variant }), className)} role="dialog" aria-modal="true" aria-labelledby="consent-title">
|
91
|
+
<div className={consentContent}>
|
92
|
+
{children}
|
93
|
+
<div className={consentActions}>
|
94
|
+
<Button size="sm" onClick={handleReject}>
|
95
|
+
{translations?.actions.reject || 'Reject'}
|
96
|
+
</Button>
|
97
|
+
<Button variant="primary" size="sm" onClick={handleAccept}>
|
98
|
+
{translations?.actions.accept || 'Accept'}
|
99
|
+
</Button>
|
100
|
+
</div>
|
101
|
+
</div>
|
102
|
+
</div>
|
103
|
+
);
|
104
|
+
};
|